﻿/*
Copyright (c) 2010 Lance Ward
This software is licensed under the MIT license, except where otherwise noted.
For complete license details, refer to license.txt
*/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using ADNLib;

namespace ADNCanvas
{
	public enum DrawingMode
	{
		Drawing,
		Animation
	}

	public partial class Canvas : Viewport
	{
		private Frame currFrame = new Frame();
		private Graphics surface = null;
		private Bitmap memImage = new Bitmap(1,1);
		private XColor defaultFill = new XColor(ColorType.Solid, Color.Blue);
		private XColor defaultLine = new XColor(ColorType.Solid, Color.Black);
		private Tool currTool = new ToolPointer();	//the default
		private bool toolLocked = false;
		private List<XObject> selObjects = new List<XObject>();
		private Matrix clearMatrix = new Matrix();
		private DrawingMode mode = DrawingMode.Drawing;
        private Bitmap onionSkin;

		public event EventHandler SelectionChanged;
		public event EventHandler DocumentModified;		//used to signal that it needs to be saved
		public event EventHandler ToolReset;			//signal to change the selected tool back to the pointer

		public Canvas()
		{
			InitializeComponent();
		}

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Frame WorkingFrame
		{
			get { return currFrame; }
			set 
            { 
                currFrame = value;
                ForceRedraw = true;
                SelectedObjects.Clear();
                SetDrawingTool(new ToolPointer(), false);
                OnToolReset();
                this.Invalidate();
            }	//TODO: look at the base res and set doc size
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Bitmap OnionSkin
		{
			get { return onionSkin; }
			set 
			{
				onionSkin = value;
				ForceRedraw = true;
				this.Invalidate();
			}
		}

		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public List<XObject> SelectedObjects
		{
			get { return selObjects; }
		}

		public DrawingMode Mode
		{
			get { return mode; }
			set { mode = value; }
		}

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Tool DrawingTool
		{
			get { return currTool; }
			set 
			{
                switch (value.ToolType)
                {
                    case DrawingToolType.Pointer:
                        SetDrawingTool(value, false);
                        break;
                    case DrawingToolType.PointEdit:
                        SetDrawingTool(value, false);
                        break;
                    case DrawingToolType.AnimPointer:
                        SetDrawingTool(value, false);
                        break;
                    case DrawingToolType.AnimPointEdit:
                        SetDrawingTool(value, false);
                        break;
                    case DrawingToolType.AnimPath:
                        SetDrawingTool(value, false);
                        break;
                    default: 
                        SetDrawingTool(value, true);
                        break;
                }
                this.Invalidate();
			}
		}

		private void SetDrawingTool(Tool tool, bool clearSelection)
		{
			if (tool != null)
			{
				if (clearSelection)
                { 
                    selObjects.Clear();
                    OnSelectionChanged();
                }

				tool.SelectedObjects = selObjects;
				tool.XObjects = currFrame.XObjects;
				tool.ParentSurface = surface;
				tool.DefaultFill = defaultFill;
				tool.DefaultLine = defaultLine;
				tool.ShapeComplete += new EventHandler<ToolEventArgs>(Tool_ShapeComplete);
				tool.ToolComplete += new EventHandler<ToolEventArgs>(Tool_ToolComplete);
				tool.CursorChange += new EventHandler<ToolEventArgs>(Tool_CursorChange);
				tool.Refresh += new EventHandler<ToolEventArgs>(Tool_Refresh);
                tool.SelectionChanged += new EventHandler(Tool_SelectionChanged);

				currTool = tool;
				
				this.Cursor = currTool.GetToolCursor();
			}
		}

		private void OnSelectionChanged()
		{
			if (SelectionChanged != null) { SelectionChanged(this, EventArgs.Empty); }
		}

		private void OnDocumentModified()
		{
			if (DocumentModified != null) { DocumentModified(this, EventArgs.Empty); }
		}

		private void OnToolReset()
		{
			if (ToolReset != null) { ToolReset(this, EventArgs.Empty); }
		}

		void Tool_Refresh(object sender, ToolEventArgs e)
		{
			if (e.ForcePaint)
			{
				ForceRedraw = true;
				this.Invalidate();
			}
			else
			{
				this.Invalidate(GetAffectedRect(), false);
			}
		}

		void Tool_CursorChange(object sender, ToolEventArgs e)
		{
			this.Cursor = e.ToolCursor;
		}

		public bool ToolLocked
		{
			get { return toolLocked; }
			set { toolLocked = value; }
		}

		void Tool_ToolComplete(object sender, ToolEventArgs e)
		{
			//switch to default tool and notify parent
			if (!toolLocked) 
			{
				SetDrawingTool(new ToolPointer(), false);
				OnToolReset();
			}
		}

		void Tool_ShapeComplete(object sender, ToolEventArgs e)
		{
			//add the shape to the list
            if (mode == DrawingMode.Drawing)
            {
                currFrame.XObjects.Add(currTool.CurrentShape);
                selObjects.Clear();
                selObjects.Add(currTool.CurrentShape);
            }
            else  //animation mode
            {
                if (selObjects.Count > 0)
                {
                    selObjects[0].Motions.Add((Motion)currTool.CurrentShape);
                }
            }

            OnSelectionChanged();
			ForceRedraw = true;
			this.Invalidate();
		}


        void Tool_SelectionChanged(object sender, EventArgs e)
        {
            OnSelectionChanged();
        }

		private void CreateSurface()
		{
			memImage = new Bitmap(this.View.Width, this.View.Height);
			surface = Graphics.FromImage(memImage);
			surface.CompositingQuality = CompositingQuality.HighQuality;
			surface.SmoothingMode = SmoothingMode.AntiAlias;
			surface.Clear(Color.Transparent);
		}

        //protected override void OnKeyDown(KeyEventArgs e)
        //{
        //    if (char.IsLetterOrDigit((char)e.KeyCode) || char.IsPunctuation((char)e.KeyCode))
        //    {
        //        currTool.OnKeyDown((char)e.KeyCode, e.Modifiers, Control.IsKeyLocked(Keys.CapsLock));
        //        this.Invalidate(GetAffectedRect());
        //    }
        //    base.OnKeyDown(e);
        //}

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            if (!char.IsControl(e.KeyChar))
            {
                currTool.OnKeyDown(e.KeyChar, Control.ModifierKeys, Control.IsKeyLocked(Keys.CapsLock));
                this.Invalidate(GetAffectedRect());
            }
        }

        protected override bool ProcessDialogKey(Keys keyData)
        {
            switch (keyData)
            {
                case Keys.Tab:
                    currTool.OnKeyDown('\t', Keys.None, Control.IsKeyLocked(Keys.CapsLock));
                    break;
                case Keys.Delete:
                    currTool.OnKeyDown(' ', Keys.Delete, false);
                    break;
                case Keys.Back:
                    currTool.OnKeyDown(' ', Keys.Back, false);
                    break;
                default:
                    break;
            }

            if (keyData != Keys.Tab && keyData != Keys.Delete) { return base.ProcessDialogKey(keyData); }
            else 
            {
                
                this.Invalidate(GetAffectedRect());
                return false; 
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
		{
            this.Focus();

            if (e.Button == MouseButtons.Right) { base.OnMouseDown(e); return; }
			currTool.MouseDown(base.TranslateIn(e.Location), e.Button, e.Clicks);	//need to translate these

			this.Invalidate(GetAffectedRect());
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			currTool.MouseMove(base.TranslateIn(e.Location), e.Button, e.Clicks);

			Rectangle rect = GetAffectedRect();
			if (rect.Height > 0 && rect.Width > 0)
			{
				this.Invalidate(rect);
			}
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			currTool.MouseUp(base.TranslateIn(e.Location), e.Button, e.Clicks);

			this.Invalidate(GetAffectedRect());
		}

		protected override void OnMouseDoubleClick(MouseEventArgs e)
		{
			currTool.MouseDoubleClick(base.TranslateIn(e.Location), e.Button, e.Clicks);

			this.Invalidate(GetAffectedRect());
		}

		private Rectangle GetAffectedRect()
		{
			if (currTool != null && currTool.CurrentShape != null)
			{
				Rectangle rect = Rectangle.Ceiling(this.TranslateOut(currTool.EraseRect));

				if (rect.Height > 0 && rect.Width > 0)
				{
					rect.Height += 10;
					rect.Width += 10;
				}
				
				return rect;
			}
			else { return new Rectangle(); }
		}

		protected override void OnPaint(PaintEventArgs e)
		{
			base.OnPaint(e);

			//this gets called after the base paint, which paints the canvas

			Graphics g = e.Graphics;

            g.Transform = clearMatrix;	//empty matrix
            DrawOnionSkin(g);
            g.Transform = OutMatrix;	//should already be the outmatrix, but just in case
		    
            if (ForceRedraw)
			{
				surface.Clear(Color.Transparent);
				Redraw();
				ForceRedraw = false;
			}
			g.Transform = clearMatrix;	//empty matrix

			g.CompositingMode = CompositingMode.SourceOver;
			g.CompositingQuality = CompositingQuality.HighSpeed;
			g.DrawImage(memImage, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);

			g.Transform = OutMatrix;

			DrawCurrent(g);
            DrawSelectedObjects(g);
            DrawSelectors(g);
            DrawPathMarkers(g);
            DrawControlPoints(g);
            DrawBezierControls(g);
            DrawAnimations(g);
		}

		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);

			if (this.Width > 0 && this.Height > 0)
			{
				CreateSurface();
				this.Invalidate();
				Redraw();
			}
		}

		private void Redraw()
		{
			
			if (currFrame != null && OutMatrix != null)
			{
				surface.Transform = OutMatrix;

				for (int i = 0; i < currFrame.XObjects.Count; i++)
				{
                    currFrame.XObjects[i].Draw(surface);
				}
			}
		}

		private void DrawOnionSkin(Graphics g)
		{
            if (onionSkin != null) 
            { g.DrawImageUnscaled(onionSkin, (int)this.XOffset, (int)this.YOffset); }
		}

		private void DrawCurrent(Graphics g)
		{
			//draw the current object - held in the currTool
			if (currTool.CurrentShape != null) 
			{ currTool.CurrentShape.DrawGhost(g); }
		}

		private void DrawSelectors(Graphics g)
		{
			//if any objects are currently selected, draw their selection points
            if (mode != DrawingMode.Animation && 
                (currTool == null || currTool.ToolType != DrawingToolType.Pointer)) { return; }

			for (int i = 0; i < selObjects.Count; i++)
			{
                selObjects[i].DrawSelectors(g, currTool.SelectorMode, mode == DrawingMode.Animation);
			}
		}

        private void DrawSelectedObjects(Graphics g)
        {
            if (currFrame != null && (currTool.ToolType == DrawingToolType.Pointer ||
                currTool.ToolType == DrawingToolType.AnimPointer))
            {
                if (currTool.Mode == ToolMode.Rotating || currTool.Mode == ToolMode.Sizing
                    || currTool.Mode == ToolMode.Moving)
                {
                    for (int i = 0; i < selObjects.Count; i++)
                    {
                        selObjects[i].DrawGhost(g);
                    }
                }
            }
        }

        private void DrawPathMarkers(Graphics g)
        {
            if (currTool == null) { return; }

            switch (currTool.ToolType)
            {
                case DrawingToolType.PointEdit:
                    ToolPointEdit tpe = (ToolPointEdit)currTool;

                    for (int i = 0; i < selObjects.Count; i++)
                    {
                        selObjects[i].DrawPathMarkers(g, tpe.SelectedPathMarkers );
                    }
                    break;
                case DrawingToolType.AnimPointEdit:
                    ToolAnimPointEdit tape = (ToolAnimPointEdit)currTool;

                    for (int i = 0; i < selObjects.Count; i++)
                    {
                        selObjects[i].DrawPathMarkers(g, tape.SelectedPathMarkers);
                    }
                    break;
                case DrawingToolType.AnimPath:
                    ToolAnimPath tap = (ToolAnimPath)currTool;

                    for (int i = 0; i < selObjects.Count; i++)
                    {
                        selObjects[i].DrawPathMarkers(g, null);
                    }
                    break;
                default:
                    return;
            }
        }
        
        private void DrawControlPoints(Graphics g)
        {
            if (currTool == null || currTool.ToolType != DrawingToolType.PointEdit){ return; }


            for (int i = 0; i < selObjects.Count; i++)
            {
                if (selObjects[i].Type == XObjectType.Shape)
                {
                    Shape s = (Shape)selObjects[i];
                    s.DrawControlPoints(g);
                }
            }
        }

        private void DrawBezierControls(Graphics g)
        {
            if (currTool == null || currTool.ToolType != DrawingToolType.PointEdit) { return; }


            for (int i = 0; i < selObjects.Count; i++)
            {
                if (selObjects[i].GetType() == typeof(ShapeBezier))
                {
                    ShapeBezier s = (ShapeBezier)selObjects[i];
                    ToolPointEdit t = (ToolPointEdit)currTool;

                    s.DrawBezierControls(g, t.SelectedSegment);
                }
            }
        }

        //only on Animation Mode
		private void DrawAnimations(Graphics g)
		{
            if (mode == DrawingMode.Animation)
            {
                for(int i = 0; i < selObjects.Count; i++)
                {
                    if (selObjects[i].Motions.Count > 0)
                    {
                        for(int j = 0; j < selObjects[i].Motions.Count; j++)
                        {
                            selObjects[i].Motions[j].Draw(g);
                        }
                    }
                }

                //TODO: Draw Frame Motions
            }
		}
	}
}
